package cn.yunbamboos.rest.service.apidoc;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.yunbamboos.Model;
import cn.yunbamboos.exception.AppException;
import cn.yunbamboos.rest.service.anno.APITag;
import cn.yunbamboos.rest.service.anno.RestServiceMapping;
import cn.yunbamboos.rest.service.apidoc.helper.ReadComponentHelper;
import cn.yunbamboos.rest.service.apidoc.helper.ReadRequestHelper;
import cn.yunbamboos.rest.service.apidoc.helper.ReadResponseHelper;
import cn.yunbamboos.rest.service.apidoc.meta.MediaType;
import cn.yunbamboos.rest.service.apidoc.meta.OpenAPI;
import cn.yunbamboos.rest.service.apidoc.meta.Operation;
import cn.yunbamboos.rest.service.apidoc.meta.Parameter;
import cn.yunbamboos.rest.service.apidoc.meta.PathItem;
import cn.yunbamboos.rest.service.apidoc.meta.Response;
import cn.yunbamboos.rest.service.core.ClassHelper;
import cn.yunbamboos.rest.service.dto.in.BaseInDTO;
import cn.yunbamboos.rest.service.dto.out.BaseOutDTO;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 生成OpenAPI3.0
 * <a href="https://openapi.apifox.cn/">OpenAPI 规范 (中文版)</a>
 */
public class OpenAPI3Generator {

    private static final Logger logger = LoggerFactory.getLogger(OpenAPI3Generator.class);

    private final String file;

    private final OpenAPI openAPI = new OpenAPI();

    private final List<Class<?>> mappings = new ArrayList<>();

    // 公用的参数
    private final List<Parameter> parameters = new ArrayList<>();

    private final List<Class<? extends Model>> components = new ArrayList<>();


    public OpenAPI3Generator(String file){
        this.file = file;
    }

    public OpenAPI getOpenAPI() {
        return openAPI;
    }

    public void addMapping(Class<?> clazz) {
        mappings.add(clazz);
    }

    public void addParameter(Parameter parameter){
        parameters.add(parameter);
    }

    public void addComponent(Class<? extends Model> clazz){
        components.add(clazz);
    }

    public void readMappings() {
        for (Class<?> clazz : mappings) {
            APITag tag = clazz.getAnnotation(APITag.class);
            if (ObjUtil.isNull(tag)) {
                throw AppException.c("未标记接口标签");
            }
            // 读取所有的方法
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                RestServiceMapping mapping = method.getAnnotation(RestServiceMapping.class);
                if (ObjUtil.isNull(mapping)) {
                    continue;
                }
                PostMapping postMapping = method.getAnnotation(PostMapping.class);

                Operation operation = new Operation();
                operation.setSummary(mapping.name());
                operation.setDescription("");
                operation.addTag(tag.name());
                // 公用参数
                if(mapping.authentication()){
                    operation.addParameters(parameters);
                }

                // 获取方法 参数类型 返回类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                Class<? extends BaseInDTO> in = ClassHelper.asSubclass(BaseInDTO.class, parameterTypes[0]);
                Class<? extends BaseOutDTO> out = ClassHelper.asSubclass(BaseOutDTO.class, method.getReturnType());
                // 请求参数
                String[] consumes = mapping.consumes();
                for (String consume : consumes){
                    ReadRequestHelper helper =  new ReadRequestHelper(in);
                    helper.read();
                    MediaType mediaType = helper.getMediaType();
                    operation.getRequestBody().put(consume, mediaType);
                }
                // 响应参数
                Response response = new Response("200");
                response.setDescription("成功");
                String[] produces =  mapping.produces();
                for (String produce : produces){
                    ReadResponseHelper helper = new ReadResponseHelper(out);
                    helper.read();
                    MediaType mediaType = helper.getMediaType();
                    response.put(produce, mediaType);
                }
                operation.addResponse(response);

                logger.info("服务: {} in: {}  out: {}", mapping.name(), in, out);

                String[] paths = postMapping.value();
                for (String path : paths) {
                    PathItem item = new PathItem(path);
                    item.setOperation("post", operation);
                    openAPI.getPaths().addPathItem(item);
                }
            }
        }
    }

    public void readComponents() {
        for (Class<? extends Model> clazz : components) {
            ReadComponentHelper helper = new ReadComponentHelper(clazz);
            helper.read();
            String key = StrUtil.format("{} {}", helper.getTableName(), helper.getTableCNName());
            openAPI.getComponents().put(key, helper.getSchema());
        }
    }

    public void generator() {
        this.readMappings();
        this.readComponents();
        JSONObject json = openAPI.toJSON();
        String str = JSON.toJSONString(json, JSONWriter.Feature.PrettyFormat);
        logger.info("\n{}", str);
        FileUtil.writeString(str, file, "UTF-8");
        logger.info("{}", file);
    }

}
